#include "ESP8266Ping.h"
#include "http_download.h"

WiFiClient client;    

#define DFDBG(...) if(0){Serial.print("["); Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);}
typedef void (*voidFunc)(void);

typedef void (*tCB)(char *data, uint16_t len);


const char *wifiSsid,*wifiPasswd;
const char *topic,*data;
char serverIp[40];
uint16_t serverPort;
uint32_t wifiBeginTime=0;
uint32_t wifiEndTime=0;

int  allowGetJson = -1;//-1 no, 0 allow, 1 alreadyCMD_GET_VERSION
bool apiUpdate = false;
bool hasUpdateFile = false;

Iot *iot=NULL;

int wifiNowState=-1;
int wifiLastState=-1;
int serverNowState = -1;
int serverLastState = -1;

bool iotConnectedServer = false;
/*
void setup()
{
  Serial.begin(38400);
  WiFi.begin("rd", "hidfrobot5353");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.println("WiFi connected");
  Serial.println("IP address: "); 
  Serial.println(WiFi.localIP());
  
}*/

uint8_t getCS(pPacketHeader packet)
{
  uint8_t cs = 0;
  uint8_t *p = (uint8_t*)packet;
  
  for(int i=0; i < packet->length + 5; i++){
    cs += p[i];
  }
  return cs;
}

size_t readN(void* buf_, size_t len)
{
  size_t offset = 0,left = len;
  uint8_t *buf = (uint8_t*)buf_;
  long long curr = millis();
  while(left){
    if(Serial.available()){
      buf[offset++] = Serial.read();
      DFDBG(buf[offset-1], HEX);
        left--;
    }
    if(millis() - curr > 200){
      break;
    }
  }
  return offset;
}

int16_t readPacketPrefix( pPacketHeader header )
{
  int16_t ret = 0;
  uint8_t ch;
  typedef enum{
    RECV_HEADER_INIT,
    RECV_HEADER_55,
    RECV_HEADER_AA,
    RECV_HEADER_FRAMENO,
    RECV_HEADER_LENGTH,
    RECV_HEADER_CMD,
    RECV_HEADER_OK
  }eRecvHeaderState;
  eRecvHeaderState state = RECV_HEADER_INIT;
  
  while(state != RECV_HEADER_OK){
    if(readN(&ch, 1) != 1){
      ret = -1;
      break;
    }

    if((ch == 0x55) && (state == RECV_HEADER_INIT)){
      header->header55 = 0x55;
      state = RECV_HEADER_55;
      continue;
    }else if((ch == 0xaa) && (state == RECV_HEADER_55)){
      header->headeraa = 0xaa;
      state = RECV_HEADER_AA;
      continue;
    }else if(state == RECV_HEADER_AA){
      header->frameNo = ch;
      state = RECV_HEADER_FRAMENO;
      continue;
    }else if(state == RECV_HEADER_FRAMENO){
      header->length = ch;
      state = RECV_HEADER_LENGTH;
      continue;
    }else if(state == RECV_HEADER_LENGTH){
      header->cmd = ch;
      state = RECV_HEADER_OK;
      continue;
    }else{DFDBG(ch,HEX);
      state = RECV_HEADER_INIT;
    }
  }
  return ret;
}


pPacketHeader mallocPacket( uint8_t cmd, const void *data, uint8_t len, bool isString ,uint8_t frameNo)
{
  const char *str = (const char *)data;
  if(isString){
    if(strlen(str) > 200){
      return NULL;
    }
  }

  pPacketHeader packet = (pPacketHeader)malloc(sizeof(tPacketHeader) + len + 1);
  if(packet == NULL){
    while(1);
  }
  memset(packet, 0, sizeof(tPacketHeader) + len + 1);
  packet->header55 = 0x55;
  packet->headeraa = 0xaa;
  packet->length = len;
  packet->cmd = cmd;
  packet->frameNo = frameNo;//++frameNo;
  memcpy(packet->payload,data,len);
  packet->payload[packet->length] = getCS(packet);
  return packet;
}



int16_t sendPacket(pPacketHeader packet)
{
  uint8_t *p = (uint8_t *)packet;
  uint8_t len = sizeof(tPacketHeader) + packet->length + 1;
  for(int i=0; i < len; i++){
    if((i == 5) || (i == 6)){
      JHDBG("%X  %c\r\n", p[i],p[i]);
    }else{
       DFDBG(p[i],HEX);
    }
    Serial.write(p[i]);
  }
  return len;
}

void freePacket(pPacketHeader packet)
{
  free(packet);
}
/*
void setColor(int red,int green,int blue)
{
  //analogWrite(REDLED,255*4-red);
  //analogWrite(BLUELED,255*4-blue);  
  //analogWrite(GREENLED,255*4-green); 
  digitalWrite(REDLED,LOW);
}
*/
//red:0 white:1,blue:2 yellow:3 green:4
void setColor(int color)
{
  if(color == 0){
    digitalWrite(REDLED,LOW);
    digitalWrite(GREENLED,HIGH);
    digitalWrite(BLUELED,HIGH);  
  }else if(color == 1){
    digitalWrite(REDLED,LOW);
    digitalWrite(GREENLED,LOW);
    digitalWrite(BLUELED,LOW);
  }else if(color == 2){
    digitalWrite(REDLED,HIGH);
    digitalWrite(GREENLED,HIGH);
    digitalWrite(BLUELED,LOW); 
  }else if(color == 3){
    digitalWrite(REDLED,LOW);
    digitalWrite(GREENLED,LOW);
    digitalWrite(BLUELED,HIGH); 
  }else if(color == 4){
    digitalWrite(REDLED,HIGH);
    digitalWrite(GREENLED,LOW);
    digitalWrite(BLUELED,HIGH);   
  }else{
    digitalWrite(REDLED,HIGH);
    digitalWrite(GREENLED,HIGH);
    digitalWrite(BLUELED,HIGH); 
  }
}

void setup() {
  //pinMode(13,OUTPUT);
  //analogWriteFreq(100);
  //analogWrite(13,255*3);
  pinMode(REDLED,OUTPUT);
  pinMode(BLUELED,OUTPUT);
  pinMode(GREENLED,OUTPUT);
  //start red
  //setColor(255*4,0,0);
  setColor(0);
 // pinMode(UPDATE_LED,OUTPUT);
  pinMode(UPDATE_BUTTON,INPUT_PULLUP);
  //digitalWrite(UPDATE_LED,LOW);
  Serial.begin(38400);
  delay(10);
  DFDBG(); 
}
void cbCommon(char *topic, char *data, uint16_t len)
{
  int topicLen;
  const char *p = strchr(strchr(topic, '/')+1, '/') + 1;
  char *buf=(char *)malloc(strlen(p)+strlen(data)+2);
  memset(buf,0,strlen(p)+strlen(data)+2);
  strcpy(buf,p);
  strcpy(&buf[strlen(buf)+1],data);
  pPacketHeader packet = mallocPacket(CMD_MODULE_SUB_DATA, buf, strlen(p)+strlen(data)+2, false, 0xfe);
  free(buf);
  sendPacket(packet);
  freePacket(packet);
}
void cb1(char *data, uint16_t len)
{DFDBG(data);
 DFDBG(len);
  cbCommon(iot->_subtopic1, data, len);
}

void cb2(char *data, uint16_t len)
{
  cbCommon(iot->_subtopic2, data, len);  
}

void cb3(char *data, uint16_t len)
{
  cbCommon(iot->_subtopic3, data, len);    
}

void cb4(char *data, uint16_t len)
{
  cbCommon(iot->_subtopic4, data, len);
}
tCB cbs[4]={cb1,cb2,cb3,cb4};

void sendUpdatePercent(uint8_t percent)
{
    uint8_t buf[2]={EVENT_CODE_UPDATE_PERCENT,percent};
    pPacketHeader packet = mallocPacket(CMD_MODULE_REPORT, buf, 2, false, 0xff);
    sendPacket(packet);
    freePacket(packet);
}


void execPacket(void* arg)
{
  uint8_t status = 0;
  
  pPacketHeader packet = (pPacketHeader) arg;
  uint8_t frameNo = packet->frameNo;
  
  if(packet->cmd == CMD_CONNECT_WIFI){
    wifiSsid = (const char *)packet->payload;
    wifiPasswd = (const char *)&packet->payload[strlen(wifiSsid)+1];
    DFDBG(wifiSsid);
    DFDBG(wifiPasswd);
    WiFi.mode(WIFI_STA);
    if(WiFi.status() == WL_CONNECTED){
      WiFi.disconnect();  
    }
    wifiBeginTime = millis();
    WiFi.begin(wifiSsid, wifiPasswd);
  }else if(packet->cmd == CMD_QUERY_WIFI){
    status = ((WiFi.status() == WL_CONNECTED)?0:0xff);
    if(status == 0){
      if(allowGetJson == -1) allowGetJson = 0;
    }
  }else if(packet->cmd == CMD_SET_SERVER){
    strcpy(serverIp, (const char *)packet->payload);
    serverPort = packet->payload[strlen(serverIp)+1] + (packet->payload[strlen(serverIp)+2]<<8);
    /*************dns*****************/
    IPAddress aResult;
    ESP8266WiFiGenericClass getip;
    status = (getip.hostByName(serverIp,aResult)?0:0xff);//hostByName return 1(suc),0 (err)
    if(status == 0){
      memset(serverIp,0,sizeof(serverIp));
      strcpy(serverIp,aResult.toString().c_str());  
    }
    /*************dns******************/
    DFDBG(serverIp);
    DFDBG(serverPort);
  }else if(packet->cmd == CMD_QUERY_SERVER){
    if(iot)
      status = (iot->connected()?0:0xff);
    else  status = 0xff;
  }else if(packet->cmd == CMD_QUERY_IP){/*
    if(WiFi.status() == WL_CONNECTED) 
      Serial.println("WiFi connected");
    else
      Serial.println("IP address: "); 
    Serial.println(WiFi.localIP());
    */
  }else if(packet->cmd == CMD_LOG_IN){
    const char * userName,*userPasswd;
    userName = (const char *)packet->payload;
    userPasswd = (const char *)&packet->payload[strlen(userName)+1];
    DFDBG(userName);
    DFDBG(userPasswd);
    if(iot == NULL){
      iot = new Iot(&client, serverIp, serverPort, userName, userPasswd);
    }
  }else if(packet->cmd == CMD_PUBLISH){
    topic = (const char *)packet->payload;
    data = (const char *)&packet->payload[strlen(topic)+1];
    uint8_t channel = (uint8_t)packet->payload[strlen(topic)+strlen(data)+2];
    DFDBG(topic);
    DFDBG(data);    
    if(iot->configPublish(topic,channel) == true){
        iot->writeRAW(topic, data,channel);
    }else{
      status = 0xff;
    }
  }else if(packet->cmd == CMD_SUBSCRIBE){
    int index;
    topic = (const char *)packet->payload;
    DFDBG(topic);
    iot->subscribe(topic, index);
    DFDBG(index);
    if(index){
      iot->setcallback(topic, cbs[index-1]);
    }
  }else if(packet->cmd == CMD_MODULE_CONNECT){
    //server disconnect yellow
    setColor(3);
    if(Ping.ping(serverIp)) {
      if(iot){
        if(!iot->connected()){
          status = (iot->connect()?0:0xff);
          if(status == 0xff){
            uint8_t buf[2]={EVENT_CODE_SERVER,EVENT_CODE_ERR};
            checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff);  
          }
        }else status = 0x0;
        if(status == 0) iotConnectedServer = true;
      }else status = 0xff;
    } else {
      status = 0xff;
    }  
  }else if(packet->cmd == CMD_RESET){
    if(WiFi.status() == WL_CONNECTED){
      WiFi.disconnect();  
    }
    packet = mallocPacket(CMD_RSPMASK | packet->cmd, &status, 1, false, frameNo);
    sendPacket(packet);
    freePacket(packet);
    voidFunc func = (voidFunc)0x40002fa0;
    func();
    while(1);
  }
  /*else if(packet->cmd == CMD_UPDATE_FW){
    apiUpdate = true;
    if(jsonBinVersion <= NOWVERSION)
      status = 0xff;
  }
  */
  else if(packet->cmd == CMD_UPGRADE_FW){
    int len = packet->length;
    memset(platformVersionReady,0,sizeof(platformVersionReady));
    memcpy(platformVersionReady,packet->payload,strlen((char *)packet->payload));//version '1.0'
    int platform = packet->payload[strlen((char *)packet->payload)+1];//platform 1
    JHDBG("verison is %s,platform is %d,strlen(payload) = %d\r\n",platformVersionReady,platform,strlen((char *)packet->payload));
    if(platform == PLATFORM_DEFAULT){
      platformReady = PLATFORM_CURRENT;
      status = (platformDownBin()?0:0xff);
    }else if(platform == PLATFORM_DFROBOT){
      platformReady = PLATFORM_DFROBOT;
      status = (platformDownBin()?0:0xff);
    }else if(platform == PLATFORM_AZURE){
      platformReady = PLATFORM_AZURE;
      status = (platformDownBin()?0:0xff); 
    }else if(platform == PLATFORM_AWS){
      platformReady = PLATFORM_AWS;
      status = (platformDownBin()?0:0xff);
    }else{
      platformReady = 0xff;  //err platform
      status = 0xff;
    }  
  }else if(packet->cmd == CMD_GET_VERSION){
    JHDBG("platformVersionReady = %d\r\n",platformVersionReady);
    char buf[10]={0};

    int nowversionTen = (int)NOWVERSION/10;
    int nowversionBits = NOWVERSION%10;
    sprintf(buf,"%d.%d",nowversionTen,nowversionBits); 

    buf[strlen(buf)+1]=0x01;
    packet = mallocPacket(CMD_RSPMASK | packet->cmd, buf, strlen(buf)+2, false, frameNo);
    sendPacket(packet);
    freePacket(packet);
    return;
  }
  packet = mallocPacket(CMD_RSPMASK | packet->cmd, &status, 1, false, frameNo);
  sendPacket(packet);
  freePacket(packet);
}

bool platformDownBin()
{
  int state = httpGetJson(client);
  if(state==1){
    if(downBin(client)){
      free(jsonBinFileUrl);
      jsonBinFileUrl = NULL;
      delay(100);
      voidFunc func = (voidFunc)WDT;
      func();
      while(1); 
    }//else return false;  
  //}else if(state == 0){
  //  return false;  //err version input  
  //}else{
  //  return false;  //err state=-1
  }
  return false;
}

pPacketHeader getUartPacket(void)
{
  tPacketHeader header;
  if(readPacketPrefix(&header) < 0){
    return NULL;
  }
  pPacketHeader packet = (pPacketHeader)malloc(sizeof(header) + header.length + 1);
  if(packet == NULL){
    while(1);
  }
  memcpy(packet, &header, sizeof(header));
  if(readN(packet->payload, packet->length + 1 ) != (size_t)(packet->length + 1)){
    free(packet);
    packet = NULL;
  }
  //else if(getCS(packet) != packet->payload[packet->length]){
  //  DFDBG("--recvRspPacket cs error---");
  //  free(packet);
  //  packet = NULL;
  //}DFDBG("\n");
  return packet;
}

void checkModuleReport(uint8_t cmd,void *buf,uint8_t len,bool isString,uint8_t cs)
{
  pPacketHeader packet = mallocPacket(cmd, buf, len, isString, cs);
  sendPacket(packet);
  freePacket(packet);
}

void checkWifi()
{
  if(WiFi.status() != WL_CONNECTED){
    wifiNowState = -1;
    //Serial.println("wifi off");
  }else{
    wifiNowState = 0;  
    //Serial.println("wifi on");
  }
  if(wifiNowState!=wifiLastState){
    wifiLastState=wifiNowState; 
    if(wifiNowState == 0){
      //uint8_t status = 0x0; 
      //wifi connect green
      setColor(1);
      uint8_t buf[2]={EVENT_CODE_WIFI,EVENT_CODE_CONNECT};
      checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff);
    }else{
      //uint8_t status = 0xFF; 
      //wifi disconnect red
      setColor(0);
      uint8_t buf[2]={EVENT_CODE_WIFI,EVENT_CODE_DISCONNECT};
      checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff); 
    }
  }
  if(wifiBeginTime != 0){
    wifiEndTime = millis();
    if(((wifiEndTime - wifiBeginTime)>=10000)&&(wifiNowState == -1)){
      uint8_t buf[2]={EVENT_CODE_WIFI,EVENT_CODE_ERR};
      checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff);
      wifiBeginTime = 0;
    }  
  }
}

void checkServer()
{
   if(iot){
    if(!iot->connected()){
      serverNowState = -1;
    }else{
      serverNowState = 0;  
    } 
    if(serverNowState!=serverLastState){
      serverLastState=serverNowState; 
      if(serverNowState == 0){
        //uint8_t status = 0x0; 
        //server connect change yellow to green
        setColor(4);
        uint8_t buf[2]={EVENT_CODE_SERVER,EVENT_CODE_CONNECT};
        checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff);
      }else{
        //uint8_t status = 0xFF; 
        //server disconnect yellow
        setColor(3);
        uint8_t buf[2]={EVENT_CODE_SERVER,EVENT_CODE_DISCONNECT};
        checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff);  
      }
    }    
  } 
}

void reconnectionServer()
{
  if(iotConnectedServer && (wifiNowState==0)){
      if(!iot->connected()){
        JHDBG("ready to reconnection server\n");
        iot->connect();  
      }
  } 
}
/*
void checkUpdate(void)
{
  static uint8_t retry = 2;
  if(retry == 0) return;
  if(jsonBinFileUrl && ((digitalRead(UPDATE_BUTTON) == 0) || apiUpdate)){
    if(downBin(client)){
      free(jsonBinFileUrl);
      jsonBinFileUrl = NULL;
      delay(100);
      voidFunc func = (voidFunc)WDT;
      func();
      while(1); 
    }else{
      retry--;
    }
  }
}
*/
void checkUpdate(void)
{
  unsigned long startTime = millis();
  unsigned long endTime;
  while(1){
    if(jsonBinFileUrl && (digitalRead(UPDATE_BUTTON) == 0)){
      if(downBin(client)){
        free(jsonBinFileUrl);
        jsonBinFileUrl = NULL;
        delay(100);
        voidFunc func = (voidFunc)WDT;
        func();
        while(1); 
      }
    }
    endTime = millis();
    if((endTime - startTime)>=10000){
      setColor(3);
      break;
    }
    delay(10);
  }
}
void findNewVersion()
{
  //find new version:blue
  setColor(2);
  static char onlyOnce = 0;
  JHDBG("jsonBinVersion = %d\n",jsonBinVersion);
  if(onlyOnce) return;
  
  if(jsonBinVersion != NOWVERSION){
    char buf[5]={0};
    buf[0] = EVENT_CODE_NEW_VERSION;
    int nowversionTen = jsonBinVersion/10;
    int nowversionBits = jsonBinVersion%10;
    sprintf(&buf[1],"%d.%d",nowversionTen,nowversionBits); 
    buf[4] = 0x00; 
    
    //uint8_t buf[2]={EVENT_CODE_NEW_VERSION,jsonBinVersion};
    checkModuleReport(CMD_MODULE_REPORT, buf, 5, false, 0xff);
    checkUpdate();
  }
  onlyOnce = 1;
}

void noNewVersion()
{
  uint8_t buf[2]={EVENT_CODE_NEW_VERSION,EVENT_CODE_VERSION_NO};
  checkModuleReport(CMD_MODULE_REPORT, buf, 2, false, 0xff);
}

void loop(){
  pPacketHeader packet;
  packet = getUartPacket();
  if(packet){
    execPacket(packet);
    freePacket(packet);
  }

  
  checkServer();
  checkWifi();
  //checkUpdate();
  reconnectionServer();

  if(allowGetJson == 0){
    if(WiFi.status() == WL_CONNECTED){
      int state = httpGetJson(client);
      if((state == 0)||(state == 1)) {DFDBG();
        allowGetJson = 1;
      }
    }
  }

  if(iot){
    iot->available(100);
  }
}
